using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace ShevaEngine.Core.Modules.Animations
{    
    /// <summary>
    /// Provides various animation utilities.
    /// </summary>
    public sealed class Util
    {
        private static Quaternion qStart, qEnd, qResult;
        private static Vector3 curTrans, nextTrans, lerpedTrans;
        private static Vector3 curScale, nextScale, lerpedScale;
        private static Matrix startRotation, endRotation;
        private static Matrix returnMatrix;

        /// <summary>
        /// Ticks per frame at 60 frames per second.
        /// </summary>
        public const long TICKS_PER_60FPS = TimeSpan.TicksPerSecond / 60;

        /// <summary>
        /// Roughly decomposes two matrices and performs spherical linear interpolation
        /// </summary>
        /// <param name="start">Source matrix for interpolation</param>
        /// <param name="end">Destination matrix for interpolation</param>
        /// <param name="slerpAmount">Ratio of interpolation</param>
        /// <returns>The interpolated matrix</returns>
        public static Matrix SlerpMatrix(Matrix start, Matrix end,
            float slerpAmount)
        {
            if (start == end)
                return start;
            // Get rotation components and interpolate (not completely accurate but I don't want 
            // to get into polar decomposition and this seems smooth enough)
            Quaternion.CreateFromRotationMatrix(ref start, out qStart);
            Quaternion.CreateFromRotationMatrix(ref end, out qEnd);
            Quaternion.Lerp(ref qStart, ref qEnd, slerpAmount, out qResult);

            // Get final translation components
            curTrans.X = start.M41;
            curTrans.Y = start.M42;
            curTrans.Z = start.M43;
            nextTrans.X = end.M41;
            nextTrans.Y = end.M42;
            nextTrans.Z = end.M43;
            Vector3.Lerp(ref curTrans, ref nextTrans, slerpAmount, out lerpedTrans);

            // Get final scale component
            Matrix.CreateFromQuaternion(ref qStart, out startRotation);
            Matrix.CreateFromQuaternion(ref qEnd, out endRotation);
            curScale.X = start.M11 - startRotation.M11;
            curScale.Y = start.M22 - startRotation.M22;
            curScale.Z = start.M33 - startRotation.M33;
            nextScale.X = end.M11 - endRotation.M11;
            nextScale.Y = end.M22 - endRotation.M22;
            nextScale.Z = end.M33 - endRotation.M33;
            Vector3.Lerp(ref curScale, ref nextScale, slerpAmount, out lerpedScale);

            // Create the rotation matrix from the slerped quaternions
            Matrix.CreateFromQuaternion(ref qResult, out returnMatrix);

            // Set the translation
            returnMatrix.M41 = lerpedTrans.X;
            returnMatrix.M42 = lerpedTrans.Y;
            returnMatrix.M43 = lerpedTrans.Z;

            // And the lerped scale component
            returnMatrix.M11 += lerpedScale.X;
            returnMatrix.M22 += lerpedScale.Y;
            returnMatrix.M33 += lerpedScale.Z;
            return returnMatrix;
        }

        /// <summary>
        /// Roughly decomposes two matrices and performs spherical linear interpolation
        /// </summary>
        /// <param name="start">Source matrix for interpolation</param>
        /// <param name="end">Destination matrix for interpolation</param>
        /// <param name="slerpAmount">Ratio of interpolation</param>
        /// <param name="result">Stores the result of hte interpolation.</param>
        public static void SlerpMatrix(
            ref Matrix start, 
            ref Matrix end,
            float slerpAmount,
            out Matrix result)
        {
            if (start == end)
            {
                result = start;
                return;
            }
            // Get rotation components and interpolate (not completely accurate but I don't want 
            // to get into polar decomposition and this seems smooth enough)
            Quaternion.CreateFromRotationMatrix(ref start, out qStart);
            Quaternion.CreateFromRotationMatrix(ref end, out qEnd);
            Quaternion.Lerp(ref qStart, ref qEnd, slerpAmount, out qResult);

            // Get final translation components
            curTrans.X = start.M41;
            curTrans.Y = start.M42;
            curTrans.Z = start.M43;
            nextTrans.X = end.M41;
            nextTrans.Y = end.M42;
            nextTrans.Z = end.M43;
            Vector3.Lerp(ref curTrans, ref nextTrans, slerpAmount, out lerpedTrans);

            // Get final scale component
            Matrix.CreateFromQuaternion(ref qStart, out startRotation);
            Matrix.CreateFromQuaternion(ref qEnd, out endRotation);
            curScale.X = start.M11 - startRotation.M11;
            curScale.Y = start.M22 - startRotation.M22;
            curScale.Z = start.M33 - startRotation.M33;
            nextScale.X = end.M11 - endRotation.M11;
            nextScale.Y = end.M22 - endRotation.M22;
            nextScale.Z = end.M33 - endRotation.M33;
            Vector3.Lerp(ref curScale, ref nextScale, slerpAmount, out lerpedScale);

            // Create the rotation matrix from the slerped quaternions
            Matrix.CreateFromQuaternion(ref qResult, out result);

            // Set the translation
            result.M41 = lerpedTrans.X;
            result.M42 = lerpedTrans.Y;
            result.M43 = lerpedTrans.Z;

            // Add the lerped scale component
            result.M11 += lerpedScale.X;
            result.M22 += lerpedScale.Y;
            result.M33 += lerpedScale.Z;
        }


        /// <summary>
        /// Determines whether or not a ModelMeshPart is skinned.
        /// </summary>
        /// <param name="meshPart">The part to check.</param>
        /// <returns>True if the part is skinned.</returns>
        public static bool IsSkinned(ModelMeshPart meshPart)
        {
            VertexElement[] ves = meshPart.VertexBuffer.VertexDeclaration.GetVertexElements();
            foreach (VertexElement ve in ves)
            {
                //(BlendIndices with UsageIndex = 0) specifies matrix indices for fixed-function vertex 
                // processing using indexed paletted skinning.
                if (ve.VertexElementUsage == VertexElementUsage.BlendIndices
                    && ve.UsageIndex == 0)
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Determines whether or not a ModelMesh is skinned.
        /// </summary>
        /// <param name="mesh">The mesh to check.</param>
        /// <returns>True if the mesh is skinned.</returns>
        public static bool IsSkinned(ModelMesh mesh)
        {
            foreach (ModelMeshPart mmp in mesh.MeshParts)
            {
                if (IsSkinned(mmp))
                    return true;
            }
            return false;
        }

        /// <summary>
        /// Determines whether or not a Model is skinned.
        /// </summary>
        /// <param name="model">The model to check.</param>
        /// <returns>True if the model is skinned.</returns>
        public static bool IsSkinned(Model model)
        {
            foreach (ModelMesh mm in model.Meshes)
            {
                if (IsSkinned(mm))
                    return true;
            }
            return false;
        }


         
    }








}
